/****************************************************************************\ 
*                                                                            *
*  SNEE (Sensor NEtwork Engine)                                              *
*  http://code.google.com/p/snee                                             *
*  Release 1.0, 24 May 2009, under New BSD License.                          *
*                                                                            *
*  Copyright (c) 2009, University of Manchester                              *
*  All rights reserved.                                                      *
*                                                                            *
*  Redistribution and use in source and binary forms, with or without        *
*  modification, are permitted provided that the following conditions are    *
*  met: Redistributions of source code must retain the above copyright       *
*  notice, this list of conditions and the following disclaimer.             *
*  Redistributions in binary form must reproduce the above copyright notice, *
*  this list of conditions and the following disclaimer in the documentation *
*  and/or other materials provided with the distribution.                    *
*  Neither the name of the University of Manchester nor the names of its     *
*  contributors may be used to endorse or promote products derived from this *
*  software without specific prior written permission.                       *
*                                                                            *
*  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS   *
*  IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, *
*  THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR    *
*  PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR          *
*  CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,     *
*  EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,       *
*  PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR        *
*  PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF    *
*  LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING      *
*  NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS        *
*  SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.              *
*                                                                            *
\****************************************************************************/
package uk.ac.manchester.cs.diasmc.common.options;

/* This code in this class taken from http://www.javaworld.com/javaworld/jw-08-2004/jw-0816-command.html?page=1,
 * and was written by Dr Matthias Laux, JavaWorld.com, 08/16/04
 * 
 * The copyright statement at http://www.javaworld.com/javaworld/common/jw-copyright.html 
 * states that "Content derived from JavaWorld may be reproduced in print or displayed online and 
 * distributed, for free, in limited quantities for nonprofit, educational purposes with proper attribution." 
 */

/**
 * This class holds the information for a <i>set</i> of options. A set can hold any number of
 * <code>OptionData</code> instances which are checked together to determine success or failure.
 * <p>
 * The approach to use this class looks like this:
 * <p>
 * <ol>
 * <li> The user uses any of the <code>Options.addSet()</code> (e. g. {@link Options#addSet(String)}) to create
 *      any number of sets required (or just relies on the default set, if only one set is required)
 * <li> The user adds all required option definitions to each set
 * <li> Using any of the <code>Options.check()</code> methods, each set can be checked whether the options
 *      that were specified on the command line satisfy its requirements
 * <li> If the check was successful for a given set, several data items are available from this class:
 *      <ul>
 *      <li> All options defined for the set (through with e. g. values, details, and multiplicity are available)
 *      <li> All data items found (these are the items on the command line which do not start with the prefix,
 *           i. e. non-option arguments)
 *      <li> All unmatched arguments on the command line (these are the items on the command line which start
 *           with the prefix, but do not match to one of the options).
 *           Programs can elect to ignore these, or react with an error
 *      </ul>
 * </ol>
 */

public class OptionSet {

    private final static String CLASS = "OptionSet";

    private java.util.ArrayList<OptionData> options = new java.util.ArrayList<OptionData>();

    private java.util.HashMap<String, OptionData> keys = new java.util.HashMap<String, OptionData>();

    private java.util.ArrayList<String> unmatched = new java.util.ArrayList<String>();

    private java.util.ArrayList<String> data = new java.util.ArrayList<String>();

    private String setName = null;

    private int minData = 0;

    private int maxData = 0;

    private Options.Prefix prefix = null;

    private Options.Multiplicity defaultMultiplicity = null;

    /**
     * Constructor
     */

    OptionSet(Options.Prefix prefix, Options.Multiplicity defaultMultiplicity,
	    String setName, int minData, int maxData) {
	if (setName == null)
	    throw new IllegalArgumentException(CLASS
		    + ": setName may not be null");
	if (minData < 0)
	    throw new IllegalArgumentException(CLASS + ": minData must be >= 0");
	if (maxData < minData)
	    throw new IllegalArgumentException(CLASS
		    + ": maxData must be >= minData");
	this.prefix = prefix;
	this.defaultMultiplicity = defaultMultiplicity;
	this.setName = setName;
	this.minData = minData;
	this.maxData = maxData;
    }

    /**
     * Get a list of all the options defined for this set
     * <p>
     * @return A list of {@link OptionData} instances defined for this set
     */

    public java.util.ArrayList<OptionData> getOptionData() {
	return options;
    }

    /**
     * Get the data for a specific option, identified by its key name (which is unique)
     * <p>
     * @param key The key for the option
     * <p>
     * @return The {@link OptionData} instance
     * <p>
     * @throws IllegalArgumentException If the <code>key</code> is <code>null</code> or unknown in this set
     */

    public OptionData getOption(String key) {
	if (key == null)
	    throw new IllegalArgumentException(CLASS + ": key may not be null");
	if (!keys.containsKey(key))
	    throw new IllegalArgumentException(CLASS + ": unknown key: " + key);
	return keys.get(key);
    }

    /**
     * Check whether a specific option is set, i. e. whether it was specified at least once on the command line.
     * <p>
     * @param key The key for the option
     * <p>
     * @return <code>true</code> or <code>false</code>, depending on the outcome of the check
     * <p>
     * @throws IllegalArgumentException If the <code>key</code> is <code>null</code> or unknown in this set
     */

    public boolean isSet(String key) {
	if (key == null)
	    throw new IllegalArgumentException(CLASS + ": key may not be null");
	if (!keys.containsKey(key))
	    throw new IllegalArgumentException(CLASS + ": unknown key: " + key);
	return keys.get(key).getResultCount() > 0 ? true : false;
    }

    /**
     * Getter method for <code>setName</code> property
     * <p>
     * @return The value for the <code>setName</code> property
     */

    public String getSetName() {
	return setName;
    }

    /**
     * Getter method for <code>minData</code> property
     * <p>
     * @return The value for the <code>minData</code> property
     */

    public int getMinData() {
	return minData;
    }

    /**
     * Getter method for <code>maxData</code> property
     * <p>
     * @return The value for the <code>maxData</code> property
     */

    public int getMaxData() {
	return maxData;
    }

    /**
     * Return the data items found (these are the items on the command line which do not start with the prefix, i. e. non-option arguments)
     * <p>
     * @return A list of strings with all data items found
     */

    public java.util.ArrayList<String> getData() {
	return data;
    }

    /**
     * Return all unmatched items found (these are the items on the command line which start with the prefix, but do not
     * match to one of the options)
     * <p>
     * @return A list of strings with all unmatched items found
     */

    public java.util.ArrayList<String> getUnmatched() {
	return unmatched;
    }

    private String test() {
    	return "test";
    }
    
    /**
     * Add a non-value option with the given key, and the default prefix and multiplicity
     * <p>
     * @param key The key for the option
     * <p>
     * @return The set instance itself (to support invocation chaining for <code>addOption()</code> methods)
     * <p>
     * @throws IllegalArgumentException If the <code>key</code> is <code>null</code> or a key with this name has already been defined
     */

    public OptionSet addOption(String key) {
	return addOption(key, defaultMultiplicity);
    }

    /**
     * Add a non-value option with the given key and multiplicity, and the default prefix
     * <p>
     * @param key          The key for the option
     * @param multiplicity The multiplicity for the option
     * <p>
     * @return The set instance itself (to support invocation chaining for <code>addOption()</code> methods)
     * <p>
     * @throws IllegalArgumentException If the <code>key</code> is <code>null</code> or a key with this name has already been defined
     *                                  or if <code>multiplicity</code> is <code>null</code>
     */

    public OptionSet addOption(String key, Options.Multiplicity multiplicity) {
	return addOption(key, false, Options.Separator.NONE, false,
		multiplicity);
    }

    /**
     * Add a value option with the given key and separator, no details, and the default prefix and multiplicity
     * <p>
     * @param key       The key for the option
     * @param separator The separator for the option
     * <p>
     * @return The set instance itself (to support invocation chaining for <code>addOption()</code> methods)
     * <p>
     * @throws IllegalArgumentException If the <code>key</code> is <code>null</code> or a key with this name has already been defined
     *                                  or if <code>separator</code> is <code>null</code>
     */

    public OptionSet addOption(String key, Options.Separator separator) {
	return addOption(key, false, separator, true, defaultMultiplicity);
    }

    /**
     * Add a value option with the given key, separator, and multiplicity, no details, and the default prefix
     * <p>
     * @param key          The key for the option
     * @param separator    The separator for the option
     * @param multiplicity The multiplicity for the option
     * <p>
     * @return The set instance itself (to support invocation chaining for <code>addOption()</code> methods)
     * <p>
     * @throws IllegalArgumentException If the <code>key</code> is <code>null</code> or a key with this name has already been defined
     *                                  or if <code>separator</code> or <code>multiplicity</code> are <code>null</code>
     */

    public OptionSet addOption(String key, Options.Separator separator,
	    Options.Multiplicity multiplicity) {
	return addOption(key, false, separator, true, multiplicity);
    }

    /**
     *
     * Add a value option with the given key and separator, possibly details, and the default prefix and multiplicity
     * <p>
     * @param key       The key for the option
     * @param details   A boolean indicating whether details are expected for the option
     * @param separator The separator for the option
     * <p>
     * @return The set instance itself (to support invocation chaining for <code>addOption()</code> methods)
     * <p>
     * @throws IllegalArgumentException If the <code>key</code> is <code>null</code> or a key with this name has already been defined
     *                                  or if <code>separator</code> is <code>null</code>
     */

    public OptionSet addOption(String key, boolean details,
	    Options.Separator separator) {
	return addOption(key, details, separator, true, defaultMultiplicity);
    }

    /**
     * Add a value option with the given key, separator, and multiplicity, possibly details, and the default prefix
     * <p>
     * @param key          The key for the option
     * @param details      A boolean indicating whether details are expected for the option
     * @param separator    The separator for the option
     * @param multiplicity The multiplicity for the option
     * <p>
     * @return The set instance itself (to support invocation chaining for <code>addOption()</code> methods)
     * <p>
     * @throws IllegalArgumentException If the <code>key</code> is <code>null</code> or a key with this name has already been defined
     *                                  or if <code>separator</code> or <code>multiplicity</code> are <code>null</code>
     */

    public OptionSet addOption(String key, boolean details,
	    Options.Separator separator, Options.Multiplicity multiplicity) {
	return addOption(key, details, separator, true, multiplicity);
    }

    /**
     * The master method to add an option. Since there are combinations which are not
     * acceptable (like a NONE separator and a true value), this method is not public.
     * Internally, we only supply acceptable combinations.
     */

    OptionSet addOption(String key, boolean details,
	    Options.Separator separator, boolean value,
	    Options.Multiplicity multiplicity) {

	if (key == null)
	    throw new IllegalArgumentException(CLASS + ": key may not be null");
	if (multiplicity == null)
	    throw new IllegalArgumentException(CLASS
		    + ": multiplicity may not be null");
	if (separator == null)
	    throw new IllegalArgumentException(CLASS
		    + ": separator may not be null");
	if (keys.containsKey(key))
	    throw new IllegalArgumentException(CLASS + ": the key " + key
		    + " has already been defined for this OptionSet");

	OptionData od = new OptionData(prefix, key, details, separator, value,
		multiplicity);
	options.add(od);
	keys.put(key, od);

	return this;

    }

}
